﻿using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace Devonline.Identity;

/// <summary>
/// Creates a new instance of a persistence store for roles.
/// </summary>
/// <typeparam name="TRole">The type of the class representing a role</typeparam>
/// <remarks>
/// Constructs a new instance of <see cref="RoleStore{TRole}"/>.
/// </remarks>
/// <param name="context">The <see cref="IdentityDbContext"/>.</param>
/// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
public class GroupStore(IdentityDbContext context, DefaultIdentityErrorDescriber describer) : GroupStore<IdentityDbContext, Group, string>(context, describer);

/// <summary>
/// Creates a new instance of a persistence store for roles.
/// </summary>
/// <typeparam name="TRole">The type of the class representing a role.</typeparam>
/// <typeparam name="TContext">The type of the data context class used to access the store.</typeparam>
/// <typeparam name="TKey">The type of the primary key for a role.</typeparam>
public class GroupStore<TContext, TGroup, TKey> :
    IGroupStore<TGroup, TKey>
    where TContext : DbContext
    where TGroup : Group<TKey>
    where TKey : IEquatable<TKey>, IConvertible
{
    protected readonly TContext _context;
    protected readonly DbSet<TGroup> _groups;
    protected readonly DefaultIdentityErrorDescriber _errorDescriber;
    private bool _disposed;

    /// <summary>
    /// Constructs a new instance of <see cref="GroupStore{TRole, TContext, TKey}"/>.
    /// </summary>
    /// <param name="context">The <see cref="DbContext"/>.</param>
    /// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
    public GroupStore(TContext context, DefaultIdentityErrorDescriber describer)
    {
        _context = context;
        _errorDescriber = describer;
        _groups = _context.Set<TGroup>();
    }

    #region implement from IIdentityStore
    /// <summary>
    /// 使用 id 获取组织单位
    /// </summary>
    /// <param name="id"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<TGroup> FindByIdAsync(TKey id, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (id.Equals(default))
        {
            throw new ArgumentNullException(nameof(id));
        }

        return (await _groups.FindAsync(new object[] { id }, cancellationToken)) ?? throw new ArgumentNullException(nameof(id), $"the group of id {id} not exist!");
    }
    /// <summary>
    /// 使用 name 获取组织单位
    /// </summary>
    /// <param name="name"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    public virtual async Task<TGroup?> FindByNameAsync(string name, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (string.IsNullOrWhiteSpace(name))
        {
            throw new ArgumentNullException(nameof(name));
        }

        return (await _groups.FirstOrDefaultAsync(x => x.Name == name, cancellationToken)) ?? throw new ArgumentNullException(nameof(name), $"the group of name {name} not exist!");
    }

    /// <summary>
    /// 修改名字
    /// </summary>
    /// <param name="group"></param>
    /// <param name="name"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual Task SetNameAsync(TGroup group, string name, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (group == null)
        {
            throw new ArgumentNullException(nameof(group));
        }

        group.Name = name;
        return Task.CompletedTask;
    }
    /// <summary>
    /// 修改昵称
    /// </summary>
    /// <param name="group"></param>
    /// <param name="alias"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual Task SetAliasAsync(TGroup group, string alias, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (group == null)
        {
            throw new ArgumentNullException(nameof(group));
        }

        group.Alias = alias;
        return Task.CompletedTask;
    }
    /// <summary>
    /// 修改头像
    /// </summary>
    /// <param name="group"></param>
    /// <param name="image"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual Task SetImageAsync(TGroup group, string image, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (group == null)
        {
            throw new ArgumentNullException(nameof(group));
        }

        group.Image = image;
        return Task.CompletedTask;
    }
    /// <summary>
    /// 修改组织类型
    /// </summary>
    /// <param name="group"></param>
    /// <param name="authorizeType"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual Task SetAuthorizeTypeAsync(TGroup group, AuthorizeType authorizeType, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (group == null)
        {
            throw new ArgumentNullException(nameof(group));
        }

        group.Type = authorizeType;
        return Task.CompletedTask;
    }
    #endregion

    #region implement from IGroupStore
    /// <summary>
    /// Find and return parent Group, if any, who has the specified <paramref name="id"/>.
    /// </summary>
    /// <param name="id">The Group id to search for.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
    /// <returns>
    /// The <see cref="Task"/> that represents the asynchronous operation, containing the group matching the specified <paramref name="id"/> if it exists.
    /// </returns>
    /// <exception cref="ArgumentNullException"></exception>
    public virtual async Task<TGroup?> GetParentGroupAsync(TKey id, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (id.Equals(default))
        {
            throw new ArgumentNullException(nameof(id));
        }

        var group = await FindByIdAsync(id, cancellationToken);
        if (group is not null && group.ParentId is not null)
        {
            return await FindByIdAsync(group.ParentId, cancellationToken);
        }

        return null;
    }
    /// <summary>
    /// Finds and returns parent Groups, if any, who has the specified <paramref name="id"/>.
    /// </summary>
    /// <param name="id">The Group id to search for.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
    /// <returns>
    /// The <see cref="Task"/> that represents the asynchronous operation, containing the groups matching the specified <paramref name="id"/> if it exists.
    /// </returns>
    /// <exception cref="ArgumentNullException"></exception>
    public virtual async Task<IList<TGroup>> GetParentGroupsAsync(TKey id, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (id.Equals(default))
        {
            throw new ArgumentNullException(nameof(id));
        }

        return await _context.GetParentsAsync<TContext, TGroup, TKey>(id);
    }
    /// <summary>
    /// Finds and returns children Group, if any, who has the specified <paramref name="id"/>.
    /// </summary>
    /// <param name="id">The Group id to search for.</param>
    /// <param name="recursion">To recursion it's children or not.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
    /// <returns>
    /// The <see cref="Task"/> that represents the asynchronous operation, containing the groups matching the specified <paramref name="id"/> if it exists.
    /// </returns>
    /// <exception cref="NotImplementedException"></exception>
    public virtual async Task<IList<TGroup>> GetChildrenGroupsAsync(TKey id, bool recursion = false, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (id.Equals(default))
        {
            throw new ArgumentNullException(nameof(id));
        }

        return await _context.GetChildrenAsync<TContext, TGroup, TKey>(id);
    }
    #endregion

    #region 数据操作方法
    /// <summary>
    /// Creates the specified group in the store.
    /// </summary>
    /// <param name="group"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<IdentityResult> CreateAsync(TGroup group, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (group is null)
        {
            throw new ArgumentNullException(nameof(group));
        }

        group.Create();
        group.Update();
        _groups.Add(group);
        await _context.SaveChangesAsync(cancellationToken);
        return IdentityResult.Success;
    }
    /// <summary>
    /// Updates the specified group in the store.
    /// </summary>
    /// <param name="group"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<IdentityResult> UpdateAsync(TGroup group, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (group is null)
        {
            throw new ArgumentNullException(nameof(group));
        }

        _groups.Attach(group);
        group.Update();
        _groups.Update(group);

        try
        {
            await _context.SaveChangesAsync(cancellationToken);
        }
        catch (DbUpdateConcurrencyException)
        {
            return IdentityResult.Failed(_errorDescriber.ConcurrencyFailure());
        }

        return IdentityResult.Success;
    }
    /// <summary>
    /// Delete the specified group from the store.
    /// </summary>
    /// <param name="group"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<IdentityResult> DeleteAsync(TGroup group, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (group is null)
        {
            throw new ArgumentNullException(nameof(group));
        }

        _groups.Remove(group);

        try
        {
            await _context.SaveChangesAsync(cancellationToken);
        }
        catch (DbUpdateConcurrencyException)
        {
            return IdentityResult.Failed(_errorDescriber.ConcurrencyFailure());
        }

        return IdentityResult.Success;
    }
    #endregion

    /// <summary>
    /// Dispose the store
    /// </summary>
    public void Dispose()
    {
        _disposed = true;
    }
    /// <summary>
    /// Throws if this class has been disposed.
    /// </summary>
    protected void ThrowIfDisposed()
    {
        if (_disposed)
        {
            throw new ObjectDisposedException(GetType().Name);
        }
    }
}